using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Poderosa.Terminal;
using Poderosa.Sessions;
using Poderosa.Protocols;
using Poderosa.View;
using System.Text.RegularExpressions;
using Centrify.DirectAudit.Util;
using Centrify.Cfw;

namespace Centrify.DirectAudit.Common.Terminal
{
    /// <summary>
    /// TextLocation tells us how to find the text we feel interested from a TerminalDocument 
    /// </summary>
    public struct TextLocation
    {
        private int m_line;
        private int m_start;
        private int m_end;
        private string m_text;

        public TextLocation(int line, int start, string text)
        {
            m_line = line;
            m_start = start;
            m_text = text;
            m_end = start + text.Length;
        }

        /// <summary>
        /// It stores the line ID within a TerminalDocument
        /// </summary>
        public int Line
        {
            get
            {
                return m_line;
            }
        }

        /// <summary>
        /// It stores the start column in the specified line
        /// </summary>
        public int Start
        {
            get
            {
                return m_start;
            }
        }

        /// <summary>
        /// It stores the end column in the specified line
        /// </summary>
        public int End
        {
            get
            {
                return m_end;
            }
        }

        /// <summary>
        /// It stores the actual text we are talking about
        /// </summary>
        public string Text
        {
            get
            {
                return m_text;
            }
        }
    }

    /// <summary>
    /// TerminalEmulator is a control based on the class TerminalControl from Poderosa.
    /// 
    /// TerminalControl lives inside the Poderosa world, which requires the initialization of different plugins in order to make it
    /// working properly.  In order to use the TerminalEmulator control without running any Poderosa plugins, minor modifiaction
    /// is made on some files.  Readme.txt lists out all the modified files.
    /// </summary>
    public class TerminalEmulator : TerminalControl
    {
        private const int FONT_SIZE = 8;
        private PoderosaWorld m_poderosa;
        private TerminalEmulatorMouseHandler _terminalEmulatorMouseHandler;
        public List<TextLocation> m_highlightLocations = null;
        private bool m_unstable = false;

        public TerminalEmulator()
        {
            m_poderosa = new PoderosaWorld(this);
            m_poderosa.TerminalSettings.RenderProfile.FontSize = FONT_SIZE;
            m_poderosa.Callback.IconNameChanged += new EventHandler<StringEventArgs>(OnIconNameChanged);
            m_poderosa.Callback.TitleChanged += new EventHandler<StringEventArgs>(OnTitleChanged);
            m_poderosa.Callback.LineReplaced += new EventHandler<LineEventArgs>(OnLineReplaced);

            _terminalEmulatorMouseHandler = new TerminalEmulatorMouseHandler(this);
            _mouseHandlerManager.AddLastHandler(_terminalEmulatorMouseHandler);
            Attach(m_poderosa as ITerminalControlHost);
        }

        public byte[] TakeSnapshot()
        {
            return m_poderosa.Terminal.TakeSnapshot();
        }

        public void RestoreSnapshot(byte[] snapshot)
        {
            m_poderosa.Terminal.RestoreSnapshot(snapshot);
            TerminalDocument doc = m_poderosa.Terminal.GetDocument();
            SetContent(doc);
            ResizeTerminal(doc.TerminalWidth, doc.TerminalHeight);
        }

        public bool IsUnstable
        {
            get
            {
                return m_unstable;
            }

            set
            {
                m_unstable = value;
            }
        }

        public void Render(Graphics g, Rectangle clipRect)
        {
            OnPaint(new PaintEventArgs(g, clipRect));
        }

        #region Private helper functions
        #region System metrics helpers
        [DllImport("user32.dll")]
        private static extern int GetSystemMetrics(int index);
        [DllImport("gdi32.dll")]
        private static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
        [DllImport("gdi32.dll", CharSet = CharSet.Unicode)]
        private static extern int GetTextExtentPoint32(IntPtr hdc, string text, int length, out SIZE size);
        [DllImport("gdi32.dll")]
        public static extern int DeleteObject(IntPtr hObject);
        [DllImport("user32.dll")]
        public static extern IntPtr GetDesktopWindow();

        [StructLayout(LayoutKind.Sequential)]
        public struct SIZE
        {
            public int width;
            public int height;
        }

        private const int SM_CXVSCROLL = 2;
        private const int SM_CXEDGE = 45;
        private const int SM_CYEDGE = 46;
        #endregion

        private Size GetPitch(string fontName, int fontSize)
        {
            Size size = new Size(0, 0);
            Graphics g = null;
            IntPtr hdc = IntPtr.Zero;
            Font font = null;
            IntPtr hFont = IntPtr.Zero;

            try
            {
                // Poderosa core code used this code snippnet to
                // calculate the pitch size
                font = new Font(fontName, fontSize);
                hFont = font.ToHfont();
                g = Graphics.FromHwnd(GetDesktopWindow());
                hdc = g.GetHdc();
                SelectObject(hdc, hFont);
                SIZE charsize1, charsize2;
                GetTextExtentPoint32(hdc, "A", 1, out charsize1);
                GetTextExtentPoint32(hdc, "AAA", 3, out charsize2);
                font.Dispose();
                int width = (charsize2.width - charsize1.width) / 2;
                int height = charsize1.height;
                size = new Size(width, height);
            }
            finally
            {
                if (g != null)
                {
                    if (hdc != IntPtr.Zero)
                    {
                        g.ReleaseHdc(hdc);
                    }
                    g.Dispose();
                }
                if (font != null)
                {
                    font.Dispose();
                }
                if (hFont != IntPtr.Zero)
                {
                    DeleteObject(hFont);
                }
            }

            return size;
        }

        public TerminalDocument Document
        {
            get
            {
                return m_poderosa.Terminal.GetDocument();
            }
        }

        /// <summary>
        /// For all the catch handler in worker thread, we need to rethrow ThreadInterruptedException and ThreadAbortException.
        /// We only handle these two exceptions in RunOuter()
        /// </summary>
        /// <param name="e"></param>
        public static void RethrowTerminationException(Exception e)
        {
            if (e is ThreadInterruptedException
                || e is ThreadAbortException)
            {
                // This thread is terminated
                throw e;
            }
        }

        #endregion

        #region Event handlers
        private void OnIconNameChanged(object sender, StringEventArgs args)
        {
            if (IconNameChanged != null)
            {
                IconNameChanged(sender, args);
            }
        }

        private void OnTitleChanged(object sender, StringEventArgs args)
        {
            if (TitleChanged != null)
            {
                TitleChanged(sender, args);
            }
        }

        private void OnLineReplaced(object sender, LineEventArgs args)
        {
            if (LineReplaced != null)
            {
                LineReplaced(sender, args);
            }
        }


        /// <summary>
        /// Resize the terminal emulator 
        /// </summary>
        /// <param name="terminalWidth"></param>
        /// <param name="terminalHeight"></param>
        private void ResizeEmulator(int terminalWidth, int terminalHeight)
        {
            int vScrollBarWidth = GetSystemMetrics(SM_CXVSCROLL);
            int controlBorderWidth = GetSystemMetrics(SM_CXEDGE);
            int controlBorderHeight = GetSystemMetrics(SM_CYEDGE);

            ITerminalSettings settings = m_poderosa.TerminalSettings;
            Poderosa.View.RenderProfile renderProfile = settings.RenderProfile.Clone() as Poderosa.View.RenderProfile;
            int fontSize = Convert.ToInt32(Math.Ceiling(renderProfile.FontSize));

            Size pitch = GetPitch(renderProfile.FontName, fontSize);
            int width = terminalWidth * pitch.Width + vScrollBarWidth + controlBorderWidth * 2;
            int height = terminalHeight * pitch.Height + controlBorderHeight * 2;

            this.Size = new Size(width, height);
        }

        internal class TerminalEmulatorMouseHandler : DefaultMouseHandler
        {
            private TerminalEmulator m_control;

            public TerminalEmulatorMouseHandler(TerminalEmulator control)
                : base("terminal emulator")
            {
                m_control = control;
            }

            public override UIHandleResult OnMouseDown(MouseEventArgs args)
            {
                return UIHandleResult.Pass;
            }
            public override UIHandleResult OnMouseMove(MouseEventArgs args)
            {
                return UIHandleResult.Pass;
            }
            public override UIHandleResult OnMouseUp(MouseEventArgs args)
            {
                if (!m_control.EnabledEx) return UIHandleResult.Pass;

                if (args.Button == MouseButtons.Right || args.Button == MouseButtons.Middle)
                {
                }

                return UIHandleResult.Pass;
            }

        }
        #endregion

        #region Overide the original Poderosa features
        /// <summary>
        /// Poderosa provides a virtual function AdjustCaret() to third party to extend
        /// the terminal control's caret behavior.  However, it does not provide any virtual
        /// function to adjust the character style.  So, we hijack the AdjustCaret() and change
        /// it to Adjust() to let Centrify terminal emulator has a chance to update the
        /// character style.
        /// </summary>
        /// <param name="caret"></param>
        /// <param name="lines"></param>
        protected override void Adjust(Caret caret, List<Poderosa.Document.GLine> lines)
        {
            base.Adjust(caret, lines);
            Poderosa.Document.GLine r = null;

            // How does the highlight work?
            //
            // User should prepare the terminal screen by repeatedly calling TerminalEmulator.Write()
            // Then, TerminalEmulator.Highlight should be set right after the last call to TerminalEmulator.Write()
            // After that, any call to TerminalEmulator.Write() or TerminalEmualtor.Clear() will reset the highlight effect
            // If we use TerminalEmulator.Highlight together with TerminalEmulator.OnLineReplaced(), we can highlight the
            // search text on the screen.  User can do the text comparison in the line replaced callback and then set the highlight
            if (m_highlightLocations != null)
            {
                for (int i = 0; i < lines.Count; i++)
                {
                    int line = lines[i].ID;
                    int length = lines[i].ToNormalString().Length;
                    foreach (TextLocation location in m_highlightLocations)
                    {
                        if (location.Line == line && location.End <= length)
                        {
                            r = lines[i].InverseRange(location.Start, location.End);
                            if (r != null)
                            {
                                lines[i] = r;
                            }
                        }
                    }
                }
            }
        }

        protected override void OnGotFocus(EventArgs args)
        {
        }

        protected override void OnDragEnter(DragEventArgs args)
        {
        }

        protected override void OnDragDrop(DragEventArgs args)
        {
        }

        protected override bool ProcessDialogKey(Keys key)
        {
            return true;
        }

        /// <summary>
        /// Timer callback to control the mouse blinking speed
        /// </summary>
        protected override void OnWindowManagerTimer()
        {
            int speed = 2; // Enter the speed from 1 to 9

            if (++_tickCount % (10 - speed) == 0)
            {
                _caret.Tick();
                CharacterDocument.InvalidatedRegion.InvalidateLine(GetTopLine().ID + _caret.Y);
                InvalidateEx();
            }
        }
        #endregion

        #region Public functions
        /// <summary>
        /// Return the terminal emulator options.
        /// </summary>
        public ITerminalEmulatorOptions Options
        {
            get
            {
                return Poderosa.TerminalEmulatorOptions.Instance;
            }
        }

        /// <summary>
        /// Clear the entire screen
        /// </summary>
        public void Clear()
        {
            // Re-initialize the internal states of all private variables inside the TerminalEmulator class
            m_poderosa.Terminal.FullReset();

            // FullReset() doesn't clear the document.  It just clear the scroll region.
            Document.Clear();
            Invalidate();
        }

        /// <summary>
        /// Display data on the screen
        /// </summary>
        /// <param name="data">The data being shown on the terminal</param>
        /// <returns></returns>
        public void Write(byte[] data)
        {
            Write(data, 0, data.Length);
        }

        /// <summary>
        /// Display data on the screen
        /// </summary>
        /// <param name="data">The buffer holding the data being shown on the terminal</param>
        /// <param name="offset">The offset location where the data start being shown</param>
        /// <param name="length">Total number of byte being shown</param>
        /// <returns></returns>
        public void Write(byte[] data, int offset, int length)
        {
            try
            {
                m_poderosa.Terminal.OnReception(new ByteDataFragment(data, offset, length));
            }
            catch (Exception e)
            {
                RethrowTerminationException(e);
                Log.Error("Poderosa failed to process the terminal data", e);
                m_unstable = true;
            }
        }

        /// <summary>
        /// Resize the terminal screen
        /// </summary>
        /// <param name="width">Number of characters in a row</param>
        /// <param name="height">Number of rows in the screen</param>
        public void ResizeTerminal(int width, int height)
        {
            try
            {
                Log.Verbose("Resize the terminal windows to {0}, {1}", width, height);

                if (width == 0 || height == 0)
                {
                    Log.Verbose("Use default terminal windows size 80, 25");
                    width = 80;
                    height = 25;
                }

                if (width > 2048)
                {
                    Log.Warning("Centrify terminal does not support terminal windows width larger than 2048.  Resetting the width to 2048");
                    width = 2048;
                }

                if (height > 512)
                {
                    Log.Warning("Centrify terminal does not support terminal windows height larger than 512.  Resetting the width to 512");
                    height = 512;
                }

                Document.Resize(width, height);

                // update the emulator size
                ResizeEmulator(width, height);

                Invalidate();
            }
            catch (Exception e)
            {
                RethrowTerminationException(e);
                Log.Error("Poderosa failed to resize the terminal window", e);
            }
        }

        /// <summary>
        /// Get the offset location of the just-processed character
        /// </summary>
        public int DataPosition
        {
            get
            {
                return m_poderosa.Callback.DataPosition;
            }
        }


        /// <summary>
        /// Set or get the Highlight locations in the terminal documents
        /// </summary>
        public List<TextLocation> HighlightLocations
        {
            get
            {
                return m_highlightLocations;
            }

            set
            {
                m_highlightLocations = value;
            }
        }
        #endregion

        #region Public events
        public event EventHandler<StringEventArgs> TitleChanged;
        public event EventHandler<StringEventArgs> IconNameChanged;
        public event EventHandler<LineEventArgs> LineReplaced;
        #endregion

        #region IDisposable Members

        private bool m_hasDisposed;

        /// <summary>
        /// Dispose managed and unmanaged resources.
        /// </summary>
        /// <param name="isDisposing"><c>true</c> if called from 
        /// user's code; else called by runtime finalizer.
        /// Managed resources should only be disposed when called
        /// from user's code.</param>
        protected override void Dispose(bool isDisposing)
        {
            if (!m_hasDisposed)
            {
                if (isDisposing)
                {
                    // Dispose managed resources
                    _terminalEmulatorMouseHandler = null;
                }

                // Dispose unmanaged resources
                // Update the FontSize and it will dispose all the fonts (GDI objects)
                // This is Poderosa implementation.  I don't want to change it too much.
                // Otherwise, I cannot easily integrate our change with their latest version.
                //m_poderosa.TerminalSettings.RenderProfile.FontSize = 0;
                base.Dispose(isDisposing);
            }
            m_hasDisposed = true;
        }

        #endregion
    }

    /// <summary>
    /// TerminalEmulatorPanel is a wrapper class on TerminalEmulator.  The panel just contains one TerminalEmulator.
    /// The TerminalEmulator control can be resized inside the panel.  If the TerminalEmulator is larger than the
    /// panel, scrollbar will be displayed
    /// </summary>
    public class TerminalEmulatorPanel : Panel
    {
        private TerminalEmulator m_vt;

        public TerminalEmulatorPanel()
        {
            m_vt = new TerminalEmulator();
            m_vt.IconNameChanged += new EventHandler<StringEventArgs>(OnIconNameChanged);
            m_vt.TitleChanged += new EventHandler<StringEventArgs>(OnTitleChanged);
            m_vt.LineReplaced += new EventHandler<LineEventArgs>(OnLineReplaced);

            this.AutoScroll = true;
            this.BackColor = System.Drawing.Color.White;
            this.Controls.Add(this.m_vt);
            this.Dock = System.Windows.Forms.DockStyle.Fill;

            this.m_vt.AllowDrop = true;
            this.m_vt.BackColor = System.Drawing.Color.White;
            this.m_vt.Cursor = System.Windows.Forms.Cursors.IBeam;
            this.m_vt.EnabledEx = true;
            this.m_vt.HighlightLocations = null;
            this.m_vt.ImeMode = System.Windows.Forms.ImeMode.NoControl;
            this.m_vt.Location = new System.Drawing.Point(0, 0);
            this.m_vt.TabStop = false;
        }

        #region Public functions
        public byte[] TakeSnapshot()
        {
            return m_vt.TakeSnapshot();
        }

        public void RestoreSnapshot(byte[] snapshot)
        {
            m_vt.RestoreSnapshot(snapshot);
        }

        public ITerminalEmulatorOptions Options
        {
            get
            {
                return m_vt.Options;
            }
        }

        /// <summary>
        /// Clear the entire screen
        /// </summary>
        public void Clear()
        {
            m_vt.Clear();
        }

        /// <summary>
        /// Display data on the screen
        /// </summary>
        /// <param name="data">The data being shown on the terminal</param>
        /// <returns></returns>
        public void Write(byte[] data)
        {
            m_vt.Write(data);
        }

        /// <summary>
        /// Display data on the screen
        /// </summary>
        /// <param name="data">The buffer holding the data being shown on the terminal</param>
        /// <param name="offset">The offset location where the data start being shown</param>
        /// <param name="length">Total number of byte being shown</param>
        /// <returns></returns>
        public void Write(byte[] data, int offset, int length)
        {
            m_vt.Write(data, offset, length);
        }

        /// <summary>
        /// Resize the terminal screen
        /// </summary>
        /// <param name="width">Number of characters in a row</param>
        /// <param name="height">Number of rows in the screen</param>
        public void ResizeTerminal(int width, int height)
        {
            // suspend layout to prevent blinking of scroll bar while terminal emulator resize
            SuspendLayout();
            try
            {
                m_vt.ResizeTerminal(width, height);
            }
            finally
            {
                ResumeLayout(true);
            }
        }

        /// <summary>
        /// Get the offset location of the just-processed character
        /// </summary>
        public int DataPosition
        {
            get
            {
                return m_vt.DataPosition;
            }
        }


        /// <summary>
        /// Set or get the Highlight locations in the terminal documents
        /// </summary>
        public List<TextLocation> HighlightLocations
        {
            get
            {
                return m_vt.HighlightLocations;
            }

            set
            {
                m_vt.HighlightLocations = value;
            }
        }
        #endregion

        #region Event handlers
        private void OnIconNameChanged(object sender, StringEventArgs args)
        {
            if (IconNameChanged != null)
            {
                IconNameChanged(sender, args);
            }
        }

        private void OnTitleChanged(object sender, StringEventArgs args)
        {
            if (TitleChanged != null)
            {
                TitleChanged(sender, args);
            }
        }

        private void OnLineReplaced(object sender, LineEventArgs args)
        {
            if (LineReplaced != null)
            {
                LineReplaced(sender, args);
            }
        }
        #endregion

        #region Public events
        public event EventHandler<StringEventArgs> TitleChanged;
        public event EventHandler<StringEventArgs> IconNameChanged;
        public event EventHandler<LineEventArgs> LineReplaced;
        #endregion
    }

    internal class PoderosaWorld : IAbstractTerminalHost, ITerminalParameter, ITerminalControlHost, ITerminalConnection, ITerminalOutput, IPoderosaSocket
    {
        private TerminalControl m_emulator;
        private ITerminalSettings m_settings;
        private AbstractTerminal m_terminal;
        private TerminalTransmission m_transmission;

        public PoderosaWorld(TerminalControl emulator)
        {
            m_emulator = emulator;
            m_settings = new TerminalSettings();
            m_terminal = AbstractTerminal.Create(new TerminalInitializeInfo(this, this));
            m_transmission = new TerminalTransmission(m_terminal, m_settings, this);
        }

        public AbstractTerminal Terminal
        {
            get
            {
                return m_terminal;
            }
        }

        public ICentrifyTerminalCallback Callback
        {
            get { return m_terminal as ICentrifyTerminalCallback; }
        }

        #region IAbstractTerminalHost Members

        public ITerminalSettings TerminalSettings
        {
            get { return m_settings; }
        }

        public TerminalTransmission TerminalTransmission
        {
            get { return m_transmission; }
        }

        public ISession ISession
        {
            get { return m_terminal as ISession; }
        }

        public Poderosa.Forms.IPoderosaMainWindow OwnerWindow
        {
            get { return null; }
        }

        public Poderosa.Protocols.ITerminalConnection TerminalConnection
        {
            get { return this; }
        }

        public TerminalControl TerminalControl
        {
            get { return m_emulator; }
        }

        public void NotifyViewsDataArrived()
        {
            m_emulator.DataArrived();
        }

        public void CloseByReceptionThread(string msg)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion

        #region ITerminalParameter Members

        public int InitialWidth
        {
            get { return 80; }
        }

        public int InitialHeight
        {
            get { return 24; }
        }

        public string TerminalType
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        public void SetTerminalName(string terminaltype)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void SetTerminalSize(int width, int height)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public bool UIEquals(ITerminalParameter t)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion

        #region ICloneable Members

        public object Clone()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion

        #region IAdaptable Members

        Poderosa.IAdaptable Poderosa.IAdaptable.GetAdapter(Type adapter)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion

        #region ITerminalControlHost Members

        ITerminalSettings ITerminalControlHost.TerminalSettings
        {
            get { return m_settings; }
        }

        Poderosa.Forms.IPoderosaMainWindow ITerminalControlHost.OwnerWindow
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        ITerminalConnection ITerminalControlHost.TerminalConnection
        {
            get { return this; }
        }

        AbstractTerminal ITerminalControlHost.Terminal
        {
            get { return m_terminal; }
        }

        TerminalTransmission ITerminalControlHost.TerminalTransmission
        {
            get { return m_transmission; }
        }

        #endregion

        #region ITerminalConnection Members

        ITerminalParameter ITerminalConnection.Destination
        {
            get { throw new Exception("The method or operation is not implemented."); }
        }

        ITerminalOutput ITerminalConnection.TerminalOutput
        {
            get { return this; }
        }

        IPoderosaSocket ITerminalConnection.Socket
        {
            get { return this; }
        }

        bool ITerminalConnection.IsClosed
        {
            get { return false; } // TODO: Check it with the current database connection
        }

        void ITerminalConnection.Close()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion

        #region ITerminalOutput Members

        void ITerminalOutput.SendBreak()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        void ITerminalOutput.SendKeepAliveData()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        void ITerminalOutput.AreYouThere()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        void ITerminalOutput.Resize(int width, int height)
        {
            m_terminal.GetDocument().Resize(width, height);
            Console.WriteLine("Terminal output resize width : {0}", width);
        }

        #endregion

        #region IPoderosaSocket Members

        void IPoderosaSocket.RepeatAsyncRead(IByteAsyncInputStream receiver)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        bool IPoderosaSocket.Available
        {
            get { return false; }
        }

        void IPoderosaSocket.ForceDisposed()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion

        #region IByteOutputStream Members

        void IByteOutputStream.Transmit(ByteDataFragment data)
        {
        }

        void IByteOutputStream.Transmit(byte[] data, int offset, int length)
        {
        }

        void IByteOutputStream.Close()
        {
        }

        #endregion
    }
}
